/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include "hal_uart.h"
/* includes (standard library, system) ---------------------------------------*/
#include <limits.h>
#include <string.h>
/* includes (other library) --------------------------------------------------*/
#include "x_config.h"

#ifdef CONFIG_DEVFS
#include <devfs.h>
#endif
/* includes (project) --------------------------------------------------------*/
#include <x_common.h>
#include <x_module.h>
#include <x_warn.h>
#include <x_bug.h>
#include <x_it.h>
#include <x_errno.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/**
 * hal_uart_open - open a uart by name
 * @name: the uart name
 *
 * return: a pointer to the uart control struct or NULL if device not found
 */
hal_uart_t *hal_uart_open(const char *name)
{
    WARN_ON(name == NULL, errno = ERRNO_HAL_UART_ILLEGAL_NAME; return NULL, "Invalid name!");

    /*
     * parent is just the first member of hal_uart_t, so we can return this even when
     * hal_open return NULL
     */

    return container_of(hal_open(name, 0), hal_uart_t, parent);
}

/**
 * hal_uart_poll_read - read from uart by polling mode
 * @uart: the uart device
 * @buf:  the receiver buffer address
 * @len:  the receiver buffer length
 *
 * return: the number of bytes really read
 */
size_t hal_uart_poll_read(hal_uart_t *uart, unsigned char *buf, size_t len)
{
    size_t i;

    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    WARN_ON(buf == NULL, errno = ERRNO_HAL_UART_ILLEGAL_BUFF; return 0, "Invalid buffer!");

    WARN_ON(uart->methods->poll_getc == NULL, errno = ERRNO_HAL_UART_ILLEGAL_OPERATION;
            return 0, "POLL mode get not supported!");

    for (i = 0; i < len; i++) {
        int ch = uart->methods->poll_getc(uart);

        if (ch == -1) {
            break;
        }

        buf[i] = (unsigned char)ch;
    }

    return i;
}

static inline size_t __uart_getc(hal_uart_t *uart, unsigned char *buf, unsigned int timeout)
{
    size_t ret;

    if (k_sem_timedwait(&uart->rxsem, timeout) != 0) {
        return 0;
    }

    if (k_mutex_timedlock(&uart->rxmux, timeout) != 0) {
        k_sem_post(&uart->rxsem);
        return 0;
    }

    ret = ring_getc(uart->rxring, buf);

    k_mutex_unlock(&uart->rxmux);

    return ret;
}

/**
 * hal_uart_getc - get a char from uart
 * @uart: the uart device
 *
 * return: -1 on fail, the char if success
 */
int hal_uart_getc(hal_uart_t *uart)
{
    unsigned char ch;
    size_t got;

    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return -1, "Invalid uart device!");

    if (uart->mode == HAL_UART_MODE_POLL) {
        return uart->methods->poll_getc(uart);
    } else {
        got = __uart_getc(uart, &ch, HAL_MAX_TIMEOUT);
    }

    return got == 0 ? -1 : (int)ch;
}

/**
 * hal_uart_poll_getc - get a char from uart
 * @uart: the uart device
 *
 * return: the number of bytes really put
 */
int hal_uart_poll_getc(hal_uart_t *uart)
{
    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    if (unlikely(uart->methods->poll_putc == NULL)) {
        errno = ERRNO_HAL_UART_ILLEGAL_OPERATION;
        return 0;
    }

    return uart->methods->poll_getc(uart);
}

int hal_uart_timed_getc(hal_uart_t *uart, size_t timeout)
{
    unsigned char ch;
    size_t got;

    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return -1, "Invalid uart device!");

    if (uart->mode == HAL_UART_MODE_POLL) {
        if (uart->methods->timed_getc != NULL) {
            return uart->methods->timed_getc(uart, timeout);
        } else {
            return -1;
        }
    } else {
        got = __uart_getc(uart, &ch, timeout);
    }

    return got == 0 ? -1 : (int)ch;
}

/**
 * hal_uart_read - read from uart by asynchronous mode
 * @uart: the uart device
 * @buf: the receiver buffer address
 * @len: the receiver buffer length
 *
 * return: the number of bytes really read
 */
size_t hal_uart_read(hal_uart_t *uart, unsigned char *buf, size_t len)
{
    size_t i;

    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    WARN_ON(buf == NULL, errno = ERRNO_HAL_UART_ILLEGAL_BUFF; return 0, "Invalid buffer!");

    if (uart->mode == HAL_UART_MODE_POLL) {
        return hal_uart_poll_read(uart, buf, len);
    }

    for (i = 0; i < len; i++) {
        if (__uart_getc(uart, &buf[i], HAL_MAX_TIMEOUT) == 0) {
            break;
        }
    }

    return len;
}

// TODO: hal_uart_timedread

/**
 * hal_uart_poll_write - write buffer to uart by polling mode
 * @uart: the uart device
 * @buf: the writer buffer address
 * @len:  the writer buffer length
 *
 * return: the number of bytes really write
 */
size_t hal_uart_poll_write(hal_uart_t *uart, const unsigned char *buf, size_t len)
{
    size_t i;

    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    WARN_ON(buf == NULL, errno = ERRNO_HAL_UART_ILLEGAL_BUFF; return 0, "Invalid buffer!");

    WARN_ON(uart->methods->poll_putc == NULL, errno = ERRNO_HAL_UART_ILLEGAL_OPERATION;
            return 0, "POLL mode put not supported!");

    for (i = 0; i < len; i++) {
        if (uart->methods->poll_putc(uart, buf[i]) != 0) {
            break;
        }
    }

    return i;
}

static inline size_t __uart_putc(hal_uart_t *uart, unsigned char ch, unsigned int timeout)
{
    size_t ret;
    bool empty;

    if (k_sem_timedwait(&uart->txsem, timeout) != 0) {
        return 0;
    }

    if (k_mutex_timedlock(&uart->txmux, timeout) != 0) {
        k_sem_post(&uart->txsem);
        return 0;
    }

    empty = ring_empty(uart->txring);

    ret = ring_putc(uart->txring, ch);

    k_mutex_unlock(&uart->txmux);

    if (empty) {
        uart->methods->tx_start(uart);
    }

    return ret;
}

/**
 * hal_uart_putc - write a char to uart
 * @uart: the uart device
 * @ch:   the char to write
 *
 * return: the number of bytes really put
 */
size_t hal_uart_putc(hal_uart_t *uart, const unsigned char ch)
{
    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    if (uart->mode == HAL_UART_MODE_POLL) {
        return uart->methods->poll_putc(uart, ch);
    }

    return __uart_putc(uart, ch, HAL_MAX_TIMEOUT);
}

/**
 * hal_uart_poll_putc - write a char to uart
 * @uart: the uart device
 * @ch:   the char to write
 *
 * return: the number of bytes really put
 */
size_t hal_uart_poll_putc(hal_uart_t *uart, const unsigned char ch)
{
    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    WARN_ON(uart->methods->poll_putc == NULL, errno = ERRNO_HAL_UART_ILLEGAL_OPERATION;
            return 0, "POLL mode put not supported!");

    return uart->methods->poll_putc(uart, ch);
}

/**
 * hal_uart_write - write buffer to uart
 * @uart: the uart device
 * @buf: the writer buffer address
 * @len:  the writer buffer length
 *
 * return: the number of bytes really write
 */
size_t hal_uart_write(hal_uart_t *uart, const unsigned char *buf, size_t len)
{
    size_t i;

    WARN_ON(uart == NULL, errno = ERRNO_HAL_UART_ILLEGAL_ID; return 0, "Invalid uart device!");

    WARN_ON(buf == NULL, errno = ERRNO_HAL_UART_ILLEGAL_BUFF; return 0, "Invalid buffer!");

    if (uart->mode == HAL_UART_MODE_POLL) {
        return hal_uart_poll_write(uart, buf, len);
    }

    for (i = 0; i < len; i++) {
        if (__uart_putc(uart, buf[i], HAL_MAX_TIMEOUT) == 0) {
            return i;
        }
    }

    return len;
}

// TODO: hal_uart_timedwrite

/**
 * hal_rx_putc - put char by uart rx-ISR or a deferred rx-ISR task
 * @uart: the uart device
 * @ch:   the char to put to the rxring
 *
 * return: NA
 */
void hal_rx_putc(hal_uart_t *uart, unsigned char ch)
{
    struct ring *ring;
    bool full;

    BUG_ON(uart == NULL, "Invalid uart device!");

    /* uart ISR must be handled in task context */

    BUG_ON(it_context(), "RX should always in task context!");

    ring = uart->rxring;

    k_mutex_lock(&uart->rxmux);

    full = ring_full(ring);

    ring_putc_force(ring, ch);

    k_mutex_unlock(&uart->rxmux);

    if (!full) {
        k_sem_post(&uart->rxsem);
    }
}

/**
 * hal_tx_getc - get char by uart tx-ISR or a deferred tx-ISR task
 * @uart: the uart device
 * @ch:   a pointer to char where put the char get from txring
 *
 * return: 1 if got a char from txring, 0 if the txring is empty
 */
size_t hal_tx_getc(hal_uart_t *uart, unsigned char *ch)
{
    size_t ret;
    struct ring *ring;

    BUG_ON(uart == NULL, "Invalid uart device!");

    /* uart ISR must be handled in task context */

    BUG_ON(it_context(), "TX should always in task context!");

    ring = uart->txring;

    k_mutex_lock(&uart->txmux);

    ret = ring_empty(ring) ? 0 : ring_getc(ring, ch);

    k_mutex_unlock(&uart->txmux);

    if (ret != 0) {
        k_sem_post(&uart->txsem);
    }

    return ret;
}

#ifdef CONFIG_DEVFS
static int __uart_read(uintptr_t data, char *buf, size_t nbyte)
{
    int ret = (int)hal_uart_read((hal_uart_t *)data, (unsigned char *)buf, nbyte);

    return ret == 0 ? -1 : ret;
}

static int __uart_write(uintptr_t data, const char *buf, size_t nbyte)
{
    int ret = (int)hal_uart_write((hal_uart_t *)data, (const unsigned char *)buf, nbyte);

    return ret == 0 ? -1 : ret;
}

static int __uart_ioctl(uintptr_t data, int request, va_list valist)
{
    return -1;
}

static const struct devfs_ops __uart_ops = {
    .read = __uart_read,
    .write = __uart_write,
    .ioctl = __uart_ioctl,
};
#endif   // CONFIG_DEVFS

static int __uart_dev_init(hal_uart_t *uart)
{
    if (unlikely(k_sem_init(&uart->rxsem, 1, 0) != 0)) {
        goto err_out;
    }

    if (unlikely(k_sem_init(&uart->txsem, HAL_UART_RING_SIZE, HAL_UART_RING_SIZE) != 0)) {
        goto err_out_destroy_rxsem;
    }

    if (unlikely(k_mutex_init(&uart->rxmux) != 0)) {
        goto err_out_destroy_txsem;
    }

    if (unlikely(k_mutex_init(&uart->txmux) != 0)) {
        goto err_out_destroy_rxmux;
    }

    uart->rxring = ring_create(HAL_UART_RING_SIZE);

    if (unlikely(uart->rxring == NULL)) {
        goto err_out_destroy_txmux;
    }

    uart->txring = ring_create(HAL_UART_RING_SIZE);

    if (unlikely(uart->txring == NULL)) {
        goto err_out_destroy_rxring;
    }

    return 0;

    ring_destroy(uart->txring);
err_out_destroy_rxring:
    ring_destroy(uart->rxring);
err_out_destroy_txmux:
    k_mutex_destroy(&uart->txmux);
err_out_destroy_rxmux:
    k_mutex_destroy(&uart->rxmux);
err_out_destroy_txsem:
    k_sem_destroy(&uart->txsem);
err_out_destroy_rxsem:
    k_sem_destroy(&uart->rxsem);
err_out:
    return -1;
}

/**
 * hal_uart_register - register a uart hal device
 * @uart:    the uart device to register
 * @name:    the uart device name
 * @methods: the uart operation methods
 * @data:    the uart operating mode flags
 *
 * return: 0 on success, negative value on error
 */
int hal_uart_register(hal_uart_t *uart, const char *name, const struct hal_uart_methods *methods,
                      uintptr_t data)
{
    int ret;

    BUG_ON(uart == NULL, "Invalid uart device!");
    BUG_ON(name == NULL, "Invalid uart device name!");
    BUG_ON(methods == NULL, "Invalid uart device methods!");

    switch (data) {
    case HAL_UART_MODE_INT:
        BUG_ON(methods->tx_start == NULL, "Invalid uart device methods");

        break;
    case HAL_UART_MODE_POLL:
        BUG_ON((methods->poll_putc == NULL) || (methods->poll_getc == NULL),
               "Invalid uart device methods");

        break;
    default:
        BUG("Invalid uart device methods");
        break;
    }

    uart->parent.name = name;
    uart->mode = (uint8_t)data;
    uart->methods = methods;

    if ((ret = __uart_dev_init(uart)) != 0) {
        return ret;
    }

    if ((ret = hal_register(&uart->parent)) != 0) {
        return ret;
    }

#ifdef CONFIG_DEVFS
    return devfs_add_file(name, &__uart_ops, (uintptr_t)uart);
#else
    return 0;
#endif
}
